import sys
import os
import datetime
import traceback
import six
from six import exec_
import builtins
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import *
from CodeEditor import CodeEditor
currentdir = os.path.split(os.path.abspath(sys.argv[0]))[0]

class ModuleDictWrapper(object):
    """Emulates a module with a dynamically compiled __dict__"""
    def __init__(self, dict):
        self.dict = dict

    def __getattr__(self, name):
        if name in self.dict:
            return self.dict[name]
        else:
            raise AttributeError
class DemoError(object):
    """Wraps and stores information about the current exception"""
    def __init__(self, exc_info):
        import copy

        excType, excValue = exc_info[:2]
        # traceback list entries: (filename, line number, function name, text)
        self.traceback = traceback.extract_tb(exc_info[2])

        # --Based on traceback.py::format_exception_only()--
        if isinstance(excType, type):
            self.exception_type = excType.__name__
        else:
            self.exception_type = excType

        # If it's a syntax error, extra information needs
        # to be added to the traceback
        if excType is SyntaxError:
            try:
                msg, (filename, lineno, self.offset, line) = excValue
            except:
                pass
            else:
                if not filename:
                    filename = "<string>"
                line = line.strip()
                self.traceback.append( (filename, lineno, "", line) )
                excValue = msg
        try:
            self.exception_details = str(excValue)
        except:
            self.exception_details = "<unprintable %s object>" & type(excValue).__name__

        del exc_info

    def __str__(self):
        ret = "Type %s \n \
        Traceback: %s \n \
        Details  : %s" % ( str(self.exception_type), str(self.traceback), self.exception_details )
        return ret
        
class DemoModules(object):
    """
    Dynamically manages the original/modified versions of a demo  module
    """
    def __init__(self, demoName):        
        self.name = demoName        
        self.fileName = f"{currentdir}/{demoName}.py"
        #              (dict , source ,  filename , description   , error information )        
        self.modules = [dict(),  ""    ,    ""     , "<original>"  ,        None]

        self.modules[0]['__file__'] = self.fileName           

        # load original module
        self.LoadFromFile(self.fileName)               


    def LoadFromFile(self, filename):
        self.modules[2] = filename
        with open(filename, "rt") as file_:
            self.LoadFromSource(file_.read())

    def LoadFromSource(self, source):
        self.modules[1] = source
        self.LoadDict()

    def LoadDict(self):
        if self.name != __name__:
            source = self.modules[1]
            description = self.modules[2]
            if six.PY2:
                description = description.encode(sys.getfilesystemencoding())

            try:
                code = compile(source, description, "exec")
                exec_(code, self.modules[0])
            except:
                self.modules[4] = DemoError(sys.exc_info())
                self.modules[0] = None
            else:
                self.modules[4] = None
    
    def GetActive(self):
        dict = self.modules[0]
        if dict is None:
            return None
        else:
            return ModuleDictWrapper(dict)
    

    def GetSource(self):        
        return self.modules[1]

    def GetFilename(self):        
        return self.modules[2]


    def GetErrorInfo(self):        
        return self.modules[4]
    
    def Delete(self):        
        self.modules[0] = None
        self.modules[1] = ""
        self.modules[2] = ""
        self.modules[3] = ""
        self.modules[4] = None

class MainWindow(QMainWindow):
    __title__ = 'PySide6 Demos'
    __treeData__ = [('Layouts',['HBoxLayout', 'VBoxLayout', 'GridLayout', 'FormLayout','StackedLayout']),
                    ('Buttons',['PushButton', 'CheckBox', 'RadioButton', 'ToolButton', 'CommandLikButton',]),                    
                    ('Input Widgets',['ComboBox','FontComboBox','LineEdit','TextEdit','PlainTextEdit','SpinBox','DobleSpinBox','TimeEdit','DateEdit','DateTimeEdit','Dial','ScrollBar','Slider','KeySequenceEdit']),
                    ('Item Widgets',['ListWidget','TreeWidget','TableWidget']),
                    ('Item Views',['ListView','TreeView','TableView','ColumnView']),
                    ('Models',['StringListModel','StandardItemModel','FileSystemModel','SqlQueryModel','SqlTableModel','SqlRelationalTableModel','SortFilterProxyModel','CustomTableModel']),
                    ('Containers',['GroupBox','ScrollArea','ToolBox','TabWidget','StackedWidget','Frame','MDIArea','DockWidget']),
                    ('Display Widgets',['Label','TextBrowser','GraphicsView','CalendarWidget','LCDNumber','ProgressBar','Line','QML']),
                    ('Dialogs',['Dialog','MessageBox','ColorDialog','FontDialog','FileDialog','InputDialog','ProgressDialog']),
                    ('Charts',['BarChart','LineChart','DateTimeAxis']),
                    ('Others',['XML','SQL','TCP','UDP','BrushStyle','Clipboard'])]
    dataModel = QStandardItemModel()
    def __init__(self):
        super(MainWindow,self).__init__()
        self.codePage = None
        self.demoPage = None
        self.setWindowIcon(QIcon(f"{currentdir}/resources/pyside_icon.ico"))
        self.setWindowTitle(self.__title__)
        splitter = QSplitter(Qt.Orientation.Horizontal)
        splitter.setChildrenCollapsible(False)
        leftWidget = QWidget()
        leftLayout = QVBoxLayout()
        #左侧树
        self.treeView = QTreeView()
        self.treeView.setStyleSheet('background-color: #ffffff;')
        self.treeView.setEditTriggers(QTreeView.EditTrigger.NoEditTriggers)
        self.treeView.clicked.connect(self.OnTreeClicked)
        self.treeView.setModel(self.dataModel)    
        self.dataModel.setHorizontalHeaderLabels([self.__title__])        
        self.searchTxt = QLineEdit()
        self.searchTxt.textChanged.connect(self.__initTree__)        
        leftLayout.addWidget(self.treeView)
        leftLayout.addWidget(QLabel('Filter Demos:'))        
        leftLayout.addWidget(self.searchTxt)
        leftWidget.setLayout(leftLayout)
        #右上代码及展示
        rightTopWidget = QWidget()
        rightTopLayout = QVBoxLayout()
        self.tabWidget = QTabWidget()   
        self.codePage = CodeEditor()   
        
        self.tabWidget.addTab(self.codePage,"Demo Code") 
        rightTopLayout.addWidget(self.tabWidget)
        rightTopWidget.setLayout(rightTopLayout)        
        #右下消息
        rightBottomWidget = QWidget()        
        rightBottomLayout = QVBoxLayout()        
        self.messageTxt = QTextEdit()
        self.messageTxt.setStyleSheet('background-color:#ffffff;')
        self.messageTxt.setReadOnly(True)
        rightBottomLayout.addWidget(QLabel("Demo Message:"))
        rightBottomLayout.addWidget(self.messageTxt)
        rightBottomWidget.setLayout(rightBottomLayout)

        splitterRight = QSplitter(Qt.Orientation.Vertical)
        splitterRight.setChildrenCollapsible(False)
        splitterRight.addWidget(rightTopWidget)
        splitterRight.addWidget(rightBottomWidget)
        splitterRight.setStretchFactor(0,8)
        splitterRight.setStretchFactor(1,2)

        splitter.addWidget(leftWidget)
        splitter.addWidget(splitterRight)
        splitter.setStretchFactor(0,2)
        splitter.setStretchFactor(1,8)
        self.setCentralWidget(splitter)
        self.__initTree__()
    
    def __initTree__(self):    
        '''初始化树'''    
        self.dataModel.removeRows(0,self.dataModel.rowCount())
        root = self.dataModel.invisibleRootItem()        
        keyword = str(self.searchTxt.text())
        
        for category, items in self.__treeData__:
            if keyword:
                items = [item for item in items if keyword.lower() in item.lower()]
            if items:
                categoryItem = QStandardItem(category)                
                root.appendRow(categoryItem)
                for child in items:
                    categoryItem.appendRow(QStandardItem(child))
        #self.treeView.expandAll()#展开所有
        self.treeView.expand(self.treeView.model().index(0,0))#默认只展开第1个

    def __getTabByName__(self,tabName):
        '''根据名字获取Tab'''
        for i in range(self.tabWidget.count()):
            if tabName == self.tabWidget.tabText(i):
                return i
        return -1
    def getAppDir(self):
        return currentdir
    def OnTreeClicked(self,index):
        '''树点击事件，加载代码及示例'''
        currentItem = self.dataModel.itemFromIndex(index)        
        if currentItem.hasChildren() == False:
            name = str(currentItem.text())
            self.OnShowMessage(f'clicked {name}')
            self.OnLoadDemo(name)
    def OnShowMessage(self,msg):
        '''显示日志'''
        self.messageTxt.append(f'{datetime.datetime.now().strftime("%H:%M:%S.%f")} {msg}')
    def OnLoadDemo(self,demoName):
        '''加载示例及代码'''     
        tmpIndex = self.tabWidget.currentIndex()   
        self.codePage.setText('')
        self.demoPage = None
        if self.tabWidget.count() == 2:
            self.tabWidget.removeTab(1)
        
        fileName = f'{currentdir}/{demoName}.py'        
        if os.path.exists(fileName) == False:            
            return        
        self.OnShowMessage(f'Loading demo code {demoName}.py...')
        self.codePage.setText( open(fileName,'r').read())

        self.demoModules = DemoModules(demoName)
        self.OnShowMessage(f'Running demo {demoName}...')
        #demo 标签页，每次移除新建
        module = self.demoModules.GetActive()
        self.demoPage = module.runDemo(self) 
        self.tabWidget.addTab(self.demoPage,"Demo")
        self.tabWidget.setCurrentIndex(tmpIndex)       
        
        
        